#include "renderer/shader.h"

#include <iostream>

#include "file.h"

Shader::Shader(std::string name, std::vector<std::string> uniformList):name(name){
    const char* vertShaderText = File::GetTextFileContents(std::string("shaders/")+name+std::string(".vert"));
    const char* geomShaderText = File::GetTextFileContents(std::string("shaders/")+name+std::string(".geom"));
    const char* fragShaderText = File::GetTextFileContents(std::string("shaders/")+name+std::string(".frag"));
    GLuint fragShaderRef;
    GLuint geomShaderRef;
    GLuint vertShaderRef;
    GLint vertShaderError;
    GLint geomShaderError;
    GLint fragShaderError;
    GLint progLinkErr;
    bool useGeomShader = true;

    if(vertShaderText==nullptr or fragShaderText==nullptr){
        std::cout<<"Error loading vertex or fragment shader, aborting...\n";
        return;
    }
    if(geomShaderText==nullptr){
        std::cout<<"No geometry shader, attempting to continues...\n";
        useGeomShader = false;
    }

    vertShaderRef = glCreateShader(GL_VERTEX_SHADER);
    if(useGeomShader){
        geomShaderRef = glCreateShader(GL_GEOMETRY_SHADER);
    }
    fragShaderRef = glCreateShader(GL_FRAGMENT_SHADER);

    glShaderSource(vertShaderRef, 1, &vertShaderText, NULL);
    if(useGeomShader){
        glShaderSource(geomShaderRef, 1, &geomShaderText, NULL);
    }
    glShaderSource(fragShaderRef, 1, &fragShaderText, NULL);

    glCompileShader(vertShaderRef);
    glGetShaderiv(vertShaderRef, GL_COMPILE_STATUS, &vertShaderError);
    if(vertShaderError==GL_FALSE){
        std::cout<<"Vertex shader "<<name<<" failed to compile\n";
        return;
    }

    if(useGeomShader){
        glCompileShader(geomShaderRef);
        glGetShaderiv(geomShaderRef, GL_COMPILE_STATUS, &geomShaderError);
        if(geomShaderError==GL_FALSE){
            std::cout<<"Geometry shader "<<name<<" failed to compile\n";
            return;
        }
    }

    glCompileShader(fragShaderRef);
    glGetShaderiv(fragShaderRef, GL_COMPILE_STATUS, &fragShaderError);
    if(fragShaderError==GL_FALSE){
        std::cout<<"Fragment shader "<<name<<" failed to compile\n";
        return;
    }

    program = glCreateProgram();

    glAttachShader(program, vertShaderRef);
    if(useGeomShader){
        glAttachShader(program, geomShaderRef);
    }
    glAttachShader(program, fragShaderRef);

    glLinkProgram(program);
    glGetProgramiv(program, GL_LINK_STATUS, &progLinkErr);
    if(progLinkErr==GL_FALSE){
        std::cout<<"Shader program "<<name<<" failed to link\n";
        return;
    }

    for(auto uniformName:uniformList){
        GLint uniform = glGetUniformLocation(program, uniformName.c_str());
        if(uniform!=-1){
            uniforms.insert(std::pair<std::string,GLint>(uniformName,uniform));
        }
        else{
            std::cout<<"Error locating uniform "<<uniformName<<" in shader "<<name<<std::endl;
        }
    }
}

void Shader::SetUniform(std::string uniformName, int numValues, float* values){
    GLint prevProgram;
    GLint uniform;
    glGetIntegerv(GL_CURRENT_PROGRAM, &prevProgram);
    glUseProgram(program);

    if(uniforms.find(uniformName)==uniforms.end()){
        std::cout<<"Uniform '"<<uniformName<<"' not found\n";
        return;
    }
    uniform = uniforms.find(uniformName)->second;

    if(numValues == 1){
        glUniform1f(uniform, *values);
    }
    else if(numValues == 3){
        glUniform3f(uniform, values[0], values[1], values[2]);
    }
    else if(numValues == 4){
        glUniform4f(uniform, values[0], values[1], values[2], values[3]);
    }
    else if(numValues == 16){
        glUniformMatrix4fv(uniform, 1, GL_FALSE, values);
    }
    else{
        std::cout<<"Unsupported number of values: "<<numValues<<", for uniform '"<<uniformName<<"'\n";
    }
    glUseProgram(prevProgram);
}

void Shader::SetUniform(std::string uniformName, int numValues, int* values){
    GLint prevProgram;
    GLint uniform;
    glGetIntegerv(GL_CURRENT_PROGRAM, &prevProgram);
    glUseProgram(program);

    if(uniforms.find(uniformName)==uniforms.end()){
        std::cout<<"Uniform '"<<uniformName<<"' not found\n";
        return;
    }
    uniform = uniforms.find(uniformName)->second;

    if(numValues == 1){
        glUniform1i(uniform, *values);
    }
    else if(numValues == 3){
        glUniform3i(uniform, values[0], values[1], values[2]);
    }
    else if(numValues == 4){
        glUniform4i(uniform, values[0], values[1], values[2], values[3]);
    }
    else{
        std::cout<<"Unsupported number of values: "<<numValues<<", for uniform '"<<uniformName<<"'\n";
    }
    glUseProgram(prevProgram);
}
